/*
 * Exploder
 * 
 * Tero Pyylampi (218437), Jussi Tuurinkoski (211594)
 *
 * Abstract base class for all the construction elements in the game
 */

#include "GameConstructionElement.h"
#include "../../../GameCamera.h"
#include "../../../effects/GameEffects.h"
#include "../../../../Common.h"
#include "../../../../Resources.h"
#include "../../../../editor/EditorLayer.h"

using namespace cocos2d;

// default constructor
GameConstructionElement::GameConstructionElement( b2World *world, cocos2d::CCLayer *layer, GameCamera *camera, bool editingMode, ElementType properties )
	: GamePhysicObject( world ), mLayer( layer ), mGameCamera( camera ), mObjectBroken( false ), mObjectInEditingMode( editingMode ), mEditModeActiveMenu( 0 ), mElementType( properties ) {
	
	for( unsigned int i = 0; i < 4; ++i ) {
		mBrokenSprites[ i ] = 0;
		mBrokenPartDurabilities[ i ] = 30.0f;
	}
}

// default destructor
GameConstructionElement::~GameConstructionElement() {
	if( mIntactSprite != 0 )
		mLayer->removeChild( mIntactSprite, true );
	if( mEditModeActiveMenu != 0 )
		mLayer->removeChild( mEditModeActiveMenu, true );
	if( mCurrentlyActive )
		dynamic_cast< EditorLayer* >( mLayer )->resetCurrentlyActiveObject();
}

// update function
void GameConstructionElement::update( cocos2d::ccTime dt ) {
	GamePhysicObject::update( dt );

	// break object if needed
	if( ! mObjectInEditingMode && getObjectType() != ELEMENT_GROUND && mObjectDurabilityLeft < 0.0f && mObjectBroken == false ) {
		mObjectBroken = true;
		breakObject();
	}

	// remove a broken part if needed
	for( unsigned int i = 0; i < mBodies->size(); ++i ) {
		if( mBrokenPartDurabilities[ i ] <= 0.0f ) {
			removeBrokenElementFromGame( i );
			--i;
		}
	}

	// editiong mode functionality
	if( mObjectInEditingMode ) {
		// set sprite position
		b2Vec2 myWorldPos = mBodies->at( 0 )->GetPosition();
		cocos2d::CCPoint myScreenPos = mGameCamera->convertWorldToScreen( myWorldPos.x, myWorldPos.y );
		mIntactSprite->setPosition( myScreenPos );
		// set sprite rotation
		float myRotation = mBodies->at( 0 )->GetAngle();
		mIntactSprite->setRotation( -myRotation * 180.0f / 3.14159f );
		
		// set active object's menu position
		if( mCurrentlyActive )
			mEditModeActiveMenu->setPosition( myScreenPos );
	}
	// game mode functionality
	else {
		// broken version
		if( mObjectDurabilityLeft < 0.0f ) {
			for( unsigned int i = 0; i < mBodies->size(); ++i ) {
				// set sprite position
				b2Vec2 myWorldPos = mBodies->at( i )->GetPosition();
				cocos2d::CCPoint myScreenPos = mGameCamera->convertWorldToScreen( myWorldPos.x, myWorldPos.y );
				mBrokenSprites[ i ]->setPosition( myScreenPos );
				// set sprite rotation
				float myRotation = mBodies->at( i )->GetAngle();
				mBrokenSprites[ i ]->setRotation( -myRotation * 180.0f / 3.14159f );
			}
		}
		// normal functionality
		else {
			// set sprite position
			b2Vec2 myWorldPos = mBodies->at( 0 )->GetPosition();
			cocos2d::CCPoint myScreenPos = mGameCamera->convertWorldToScreen( myWorldPos.x, myWorldPos.y );
			mIntactSprite->setPosition( myScreenPos );
			// set sprite rotation
			float myRotation = mBodies->at( 0 )->GetAngle();
			mIntactSprite->setRotation( -myRotation * 180.0f / 3.14159f );
		}
	}
}

// game mode related functions

// apply explosion force, the object itself should decide whether to break or not
void GameConstructionElement::applyExplosionToElements( b2Vec2 sourcePos, float force ) {
	// already broken
	if( mObjectDurabilityLeft < 0.0f ) {
		// calculate force to every body part
		std::vector< b2Body* > *b = getBodyObjects();
		for( unsigned int i = 0; i < b->size(); ++i ) {
			b2Vec2 explosionImpulse;
			explosionImpulse.Set(
			b->at( i )->GetPosition().x - sourcePos.x,
			b->at( i )->GetPosition().y - sourcePos.y );
			explosionImpulse.Normalize();
			explosionImpulse *= ( 1000.0f * force / std::sqrt( std::pow( b->at( i )->GetPosition().x - sourcePos.x, 2 ) + std::pow( b->at( i )->GetPosition().y - sourcePos.y, 2 ) ) );
			if( explosionImpulse.LengthSquared() > 9.0f ) 
				b->at( i )->ApplyLinearImpulse( explosionImpulse, b->at( i )->GetPosition() );

			// TODO: check whether to remove this body from the game as it takes too much damage
		}
	}
	// check whether this object should break or at least change the sprite
	else {
		// calculate the single force
		b2Body *mBody = getBodyObjects()->at( 0 );
		b2Vec2 explosionImpulse;
			explosionImpulse.Set(
			mBody->GetPosition().x - sourcePos.x,
			mBody->GetPosition().y - sourcePos.y );
		explosionImpulse.Normalize();
		explosionImpulse *= ( 1000.0f * force / std::sqrt( std::pow( mBody->GetPosition().x - sourcePos.x, 2 ) + std::pow( mBody->GetPosition().y - sourcePos.y, 2 ) ) );
	
		// decrease life left
		mObjectDurabilityLeft -= explosionImpulse.LengthSquared();

		// check whether to break the object
		if( mObjectDurabilityLeft <= 0.0f ) {
			// break object
			mObjectBroken = true;
			breakObject();

			// recall this function to apply force to broken parts
			applyExplosionToElements( sourcePos, force );
		}
		// not yet broken, apply force and possibly change the sprite
		else {
			// possibly change the sprite
			if( mObjectDurabilityLeft / mObjectMaxDurability <= 0.75f )
				changeSpriteToBroken();

			// apply force
			mBody->ApplyLinearImpulse( explosionImpulse, mBody->GetPosition() );
		}
	}
}

// change object sprite to broken
void GameConstructionElement::changeSpriteToBroken() {}

// break object into pieces
void GameConstructionElement::breakObject() {}

// object takes damage as a result of a collision
void GameConstructionElement::takeDamage( float damage, b2Body *bodyTakingDamage ) {
	// normally taking damage
	if( mObjectDurabilityLeft > 0.0f ) {
		mObjectDurabilityLeft -= damage;
	}
	// object already broken and a single part is taking damage
	else if( mObjectBroken ) {
		// find the correct part
		for( unsigned int i = 0; i < mBodies->size(); ++i ) {
			if( mBodies->at( i ) == bodyTakingDamage ) {
				mBrokenPartDurabilities[ i ] -= damage;
				break;
			}
		}
	}
}

// get the information of this object
bool GameConstructionElement::isThisObjectPreventingVictory() {
	float distanceToInitialPosition = calculateDistanceTo( mInitialPosition );
	if( mElementType == MUST_BE_MOVED && distanceToInitialPosition < OBJECT_MOVING_TRESHOLD * 2.0f && mObjectDurabilityLeft > 0.0f )
		return true;
	return false;
}

// get the information of this object
bool GameConstructionElement::isThisObjectBrokenOrMoved() {
	float distanceToInitialPosition = calculateDistanceTo( mInitialPosition );
	if( distanceToInitialPosition >= OBJECT_MOVING_TRESHOLD || mObjectDurabilityLeft <= 0.0f )
		return true;
	return false;
}


// editing mode related functions
	
// activate or deactivate object
void GameConstructionElement::setObjectActive( bool active ) {
	// enable active object
	if( active && mCurrentlyActive == false ) {
		mCurrentlyActive = true;

		// create menu with an origin
		mEditModeActiveMenu = CCMenu::node();
		b2Vec2 myWorldPos = mBodies->at( 0 )->GetPosition();
		cocos2d::CCPoint myScreenPos = mGameCamera->convertWorldToScreen( myWorldPos.x, myWorldPos.y );
		mEditModeActiveMenu->setPosition( myScreenPos );
		mLayer->addChild( mEditModeActiveMenu, Z_LAYER_INTERFACE );
		createEditMenuItems();
	}
	// disable active object
	else if( active == false && mCurrentlyActive ) {
		mCurrentlyActive = false;

		// disable menu
		mLayer->removeChild( mEditModeActiveMenu, true );
	}
}

// whether this object is active
bool GameConstructionElement::isObjectActive() {
	return mCurrentlyActive;
}

// remove item
void GameConstructionElement::removeItem( cocos2d::CCObject *pSender ) {
	mCanBeRemoved = true;
}

// rotate item
void GameConstructionElement::rotateItem( cocos2d::CCObject *pSender ) {
	mBodies->at( 0 )->SetTransform( mBodies->at( 0 )->GetPosition(), mBodies->at( 0 )->GetAngle() - 3.14159265359f / 8.0f );
}

// switch block state
void GameConstructionElement::switchState( cocos2d::CCObject *pSender ) {
	CCMenuItemToggle *item = dynamic_cast< CCMenuItemToggle* >( pSender );
	if( item->getSelectedIndex() == 0 ) mElementType = NEUTRAL;
	else if( item->getSelectedIndex() == 1 ) mElementType = MUST_BE_MOVED;
	else mElementType = SHOULD_NOT_MOVE;
}

// get element state
ElementType GameConstructionElement::getElementStateType() {
	return mElementType;
}

// set sprite colours to red
void GameConstructionElement::setRedSpriteColors() {
	cocos2d::ccColor3B c = { 255, 0, 0 };
	mIntactSprite->setColor( c );
	for( unsigned int i = 0; i < 4; ++i ) 
		if( mBrokenSprites[ i ] != 0 )
			mBrokenSprites[ i ]->setColor( c );
}

// remove a single broken element from game
void GameConstructionElement::removeBrokenElementFromGame( int element ) {
	// show effect
	cocos2d::CCParticleSystem *s = GameEffects::createSmokeEffect( mBrokenSprites[ element ]->getPositionX(), mBrokenSprites[ element ]->getPositionY() );
	mLayer->addChild( s, Z_LAYER_GAME_EFFECTS );
	// destroy body & sprite
	mGameWorld->DestroyBody( mBodies->at( element ) );
	mBodies->erase( mBodies->begin() + element );
	mLayer->removeChild( mBrokenSprites[ element ], true );
	for( unsigned int i = element; i < 3; ++i ) {
		mBrokenSprites[ i ] = mBrokenSprites[ i + 1 ];
		mBrokenPartDurabilities[ i ] = mBrokenPartDurabilities[ i + 1 ];
	}
	mBrokenSprites[ 3 ] = 0;
	mBrokenPartDurabilities[ 3 ] = 0;
}

// helper function to create menu items
void GameConstructionElement::createEditMenuItems() {
	// remove button
	CCMenuItemImage *removeItemButton = CCMenuItemImage::itemFromNormalImage( getResource( Textures::GAME_INTERFACE_EXIT ), getResource( Textures::GAME_INTERFACE_EXIT_PRESSED ), this, menu_selector( GameConstructionElement::removeItem ) );
	removeItemButton->setScale( RATEX * 0.3f );
	removeItemButton->setPosition( WIN_WIDTH * 0.04f, WIN_HEIGHT * 0.05f );
	mEditModeActiveMenu->addChild( removeItemButton, 0 );

	// rotate button
	CCMenuItemImage *rotateItemCWButton = CCMenuItemImage::itemFromNormalImage( getResource( Textures::GAME_INTERFACE_ROTATE ), getResource( Textures::GAME_INTERFACE_ROTATE_PRESSED ), this, menu_selector( GameConstructionElement::rotateItem ) );
	rotateItemCWButton->setScale( RATEX * 0.3f );
	rotateItemCWButton->setPosition( WIN_WIDTH * 0.04f, -WIN_HEIGHT * 0.05f );
	mEditModeActiveMenu->addChild( rotateItemCWButton, 0 );

	// switch element type -toggle button
	CCMenuItemImage *buttonNeutral = CCMenuItemImage::itemFromNormalImage( getResource( Textures::GAME_INTERFACE_STATE_NEUTRAL ), getResource( Textures::GAME_INTERFACE_STATE_NEUTRAL ) );
	CCMenuItemImage *buttonMovable = CCMenuItemImage::itemFromNormalImage( getResource( Textures::GAME_INTERFACE_STATE_MOVABLE ), getResource( Textures::GAME_INTERFACE_STATE_MOVABLE ) );
	CCMenuItemImage *buttonImmovable = CCMenuItemImage::itemFromNormalImage( getResource( Textures::GAME_INTERFACE_STATE_IMMOVABLE ), getResource( Textures::GAME_INTERFACE_STATE_IMMOVABLE ) );
	CCMenuItemToggle *stateToggle = CCMenuItemToggle::itemWithTarget( this, menu_selector( GameConstructionElement::switchState ), buttonNeutral, buttonMovable, buttonImmovable, NULL );
	stateToggle->setScale( RATEX * 0.3f );
	stateToggle->setPosition( -WIN_WIDTH * 0.04f, -WIN_HEIGHT * 0.05f );
	mEditModeActiveMenu->addChild( stateToggle, 0 );
	if( mElementType == NEUTRAL ) stateToggle->setSelectedIndex( 0 );
	else if( mElementType == MUST_BE_MOVED ) stateToggle->setSelectedIndex( 1 );
	else stateToggle->setSelectedIndex( 2 );
}
